﻿using System;
using T0yK4T.IO;
using System.Net;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using T0yK4T.Tools;
using ToyChat.Client;
using T0yK4T.Configuration;

namespace ToyChat.BuiltinModules
{
    /// <summary>
    /// Interaction logic for CorePane.xaml
    /// </summary>
    public partial class CorePane : UserControl, IPane
    {
        private IUserInterface ui;
        private Menu mainMenu;
        private MenuItem fileItem = new MenuItem { Header = "File" };
        private MenuItem closeItem = new MenuItem { Header = "Close" };
        private MenuItem channelItem = new MenuItem { Header = "Channel" };
        private MenuItem channelModItem = new MenuItem { Header = "Modify" };
        private MenuItem channelJoinItem = new MenuItem { Header = "Join" };
        private MenuItem channelCreateIem = new MenuItem { Header = "Create" };
        private MenuItem channelDeleteItem = new MenuItem { Header = "Delete" };

        private static readonly ConfigurableValue<string> lastUsername = Config.GetValue<string>("lastusername", new StringConverter(), string.Empty);
        private static readonly ConfigurableValue<string> lastNick = Config.GetValue<string>("lastnick", new StringConverter(), string.Empty);
        //private static readonly ConfigurableValue<string> lastPassword = new ConfigurableStringValue("lastpassword", string.Empty);
        private static readonly ConfigurableValue<ushort> lastPort = Config.GetValue<ushort>("lastport", new UInt16Converter(), 5000);
        private static readonly ConfigurableValue<string> lastAddress = Config.GetValue<string>("lastHost", new StringConverter(), IPAddress.None.ToString());

        private Paragraph toplevelParagraph = new Paragraph();
        private IConnectionSettings settings = new ConnectionSettings { Nickname = lastNick, Password = null, Username = lastUsername, RemoteAddress = lastAddress, RemotePort = lastPort };
        private ConnectionSetup setupWindow = new ConnectionSetup();
        private CreateChannelDialog createChannel = new CreateChannelDialog();
        private JoinChannelDialog joinChannel = new JoinChannelDialog();

        private List<object> backList = new List<object>();
        private List<object> headerBackList = new List<object>();

        ///// <summary>
        ///// Saves the old header when ReplaceContent is called
        ///// </summary>
        //private object oldHeader;

        private string title = "Main";
        private Core core;

        /// <summary>
        /// Initializes a new (empty) instance of <see cref="CorePane"/>
        /// </summary>
        public CorePane()
        {
            InitializeComponent();
            this.setupWindow.Settings = this.settings;
            
            this.closeItem.Click += new RoutedEventHandler(closeItem_Click);

            Application.Current.Exit += new ExitEventHandler(Current_Exit);
            this.fileItem.Items.Add(this.closeItem);
            this.messageBox1.Document.Blocks.Add(this.toplevelParagraph);

            this.setupWindow.OK += new RoutedEventHandler(setupWindow_OK);
            this.setupWindow.Cancel += new RoutedEventHandler(setupWindow_Cancel);

            this.createChannel.OK += new RoutedEventHandler(createChannel_OK);
            this.createChannel.Cancel += new RoutedEventHandler(createChannel_Cancel);

            this.joinChannel.OK += new RoutedEventHandler(joinChannel_OK);
            this.joinChannel.Cancel += new RoutedEventHandler(joinChannel_Cancel);

            this.channelJoinItem.Click += new RoutedEventHandler(this.clistJoin1_Click);
            this.channelModItem.Click += new RoutedEventHandler(this.clistModify1_Click);
            this.channelDeleteItem.Click += new RoutedEventHandler(this.clistDelete1_Click);
            this.channelCreateIem.Click += new RoutedEventHandler(this.createButton_Click);

            this.channelItem.Items.Add(this.channelJoinItem);
            this.channelItem.Items.Add(new Separator());
            this.channelItem.Items.Add(this.channelCreateIem);
            this.channelItem.Items.Add(this.channelModItem);
            this.channelItem.Items.Add(this.channelDeleteItem);
            
            this.ReplaceContent(this.setupWindow, "Connect To");
        }

        /// <summary>
        /// Called when the JoinChannelDialog gets canceled
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The arguments of the event</param>
        void joinChannel_Cancel(object sender, RoutedEventArgs e)
        {
            this.ResetContentBox();
        }

        /// <summary>
        /// Called when the JoinChannelDialog gets confirmed
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The arguments of the event</param>
        void joinChannel_OK(object sender, RoutedEventArgs e)
        {
            this.ResetContentBox();
            this.core.JoinChannel(this.joinChannel.ChannelName, this.joinChannel.Password);
        }

        /// <summary>
        /// Initializes a new instance of <see cref="CorePane"/> and sets it's core to the specified value
        /// </summary>
        /// <param name="core">The core to use...</param>
        public CorePane(Core core)
            : this()
        {
            this.core = core;
            this.core.ServerInfoUpdated += new Action<ServerInfoMessage>(core_ServerInfoUpdated);
            this.core.ChannelListUpdated += new Action<ChannelList>(core_ChannelListUpdated);
            if (this.core.Client != null)
            {
                this.core.Client.Connected += new ClientConnectedHandler(Client_Connected);
                this.core.Client.Disconnected += new ClientDisconnectedHandler(Client_Disconnected);
            }
        }

        /// <summary>
        /// Called when the client has been disconnected
        /// </summary>
        /// <param name="sender">The client that was disconnected</param>
        /// <param name="reason">The reason</param>
        void Client_Disconnected(IClient sender, DisconnectReason reason)
        {
            if (!this.CheckAccess())
                this.Dispatcher.Invoke(new Action<IClient, DisconnectReason>((c, d) => this.Client_Disconnected(c, d)), sender, reason);
            else
            {
                this.ReplaceContent(this.setupWindow, "Connect To");
                this.mainMenu.Items.Remove(this.channelItem);
            }
        }

        /// <summary>
        /// Attempst to refocus the content of the <see cref="mainContentBox1"/>
        /// </summary>
        private void FocusContent(IDialog dialog)
        {
            if (dialog != null)
            {
                Keyboard.Focus(dialog.MainInput);
                dialog.MainInput.Focus();
            }
        }

        /// <summary>
        /// Stores the current content and header of the maincontentbox and replaces the old values with the ones specified
        /// </summary>
        /// <param name="newContent">The object that should be the new content of the main content box</param>
        /// <param name="header">The new header of the content box</param>
        private void ReplaceContent(object newContent, object header)
        {
            this.backList.Add(this.mainContentBox1.Content);
            this.headerBackList.Add(this.mainContentBox1.Header);
            this.mainContentBox1.Content = newContent;
            this.mainContentBox1.Header = header;

            if (newContent is IDialog)
                this.FocusContent((IDialog)newContent);
        }

        /// <summary>
        /// Fired when the client has been connected
        /// </summary>
        /// <param name="sender">The client that was connected</param>
        void Client_Connected(IClient sender)
        {
            if (!this.CheckAccess())
                this.Dispatcher.Invoke(new Action<IClient>(this.Client_Connected), sender);
            else
            {
                this.core.UpdateChannelList();
                this.mainMenu.Items.Add(this.channelItem);
            }
        }

        /// <summary>
        /// Called when the <see cref="Core"/> has received a channel list object from the server
        /// </summary>
        /// <param name="obj">The <see cref="ChannelList"/> that was received</param>
        void core_ChannelListUpdated(ChannelList obj)
        {
            if (!this.CheckAccess())
                this.Dispatcher.Invoke(new Action<ChannelList>((c) => this.core_ChannelListUpdated(c)), obj);
            else
            {
                this.channelView1.Items.Clear();
                foreach (ChannelDescriptor descriptor in obj.ChannelDescriptors)
                    this.channelView1.Items.Add(descriptor);
            }
        }

        /// <summary>
        /// Called whenever the <see cref="Core"/> has received an updated version of the server informations
        /// </summary>
        /// <param name="obj"></param>
        void core_ServerInfoUpdated(ServerInfoMessage obj)
        {
            if (!this.CheckAccess())
                this.Dispatcher.Invoke(new Action<ServerInfoMessage>((s) => this.core_ServerInfoUpdated(s)), obj);
            else
            {
                this.Title = obj.Name;
                this.WriteLine("[Server: {0}]: {1}", new SolidColorBrush(Colors.RoyalBlue), obj.Name, obj.MotD);
            }
        }

        /// <summary>
        /// Called when the current application closes
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The <see cref="ExitEventArgs"/> of the event</param>
        void Current_Exit(object sender, ExitEventArgs e)
        {
            if (this.core.Client != null)
                this.core.Client.Disconnect();
        }

        /// <summary>
        /// Called whenever the close menu item is clicked
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void closeItem_Click(object sender, RoutedEventArgs e)
        {
            Application.Current.Shutdown();
        }

        /// <summary>
        /// Called when the connection setup window was canceled
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The arguments of the event</param>
        void setupWindow_Cancel(object sender, RoutedEventArgs e)
        {
            if (this.core.Client.IsConnected)
                this.ResetContentBox();
        }

        /// <summary>
        /// Called when the setup window gets confirmed
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The arguments of the event</param>
        void setupWindow_OK(object sender, RoutedEventArgs e)
        {
            this.settings = this.setupWindow.Settings;
            this.SetAndStore(this.settings);
            System.Threading.ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback((o) =>
            {
                if (this.core.Client.IsConnected)
                    this.core.Client.Disconnect();
                this.core.Client.ConnectTo((IConnectionSettings)o);
            }), this.settings);
            this.ResetContentBox();
        }

        /// <summary>
        /// Stores the specified <see cref="IConnectionSettings"/> in the application configuration file
        /// </summary>
        /// <param name="settings"></param>
        private void SetAndStore(IConnectionSettings settings)
        {
            lastAddress.Value = settings.RemoteAddress;
            lastNick.Value = settings.Nickname;
            //lastPassword.Value = settings.Password;
            lastPort.Value = settings.RemotePort;
            lastUsername.Value = settings.Username;
            
            lastAddress.Store();
            lastNick.Store();
            //lastPassword.Store();
            lastPort.Store();
            lastUsername.Store();
        }

        /// <summary>
        /// Called from the UI that this pane was created on
        /// </summary>
        /// <param name="ui">The <see cref="IUserInterface"/> that contains this pane</param>
        public void Initialize(IUserInterface ui)
        {
            this.ui = ui;
            if (this.ui != null)
            {
                this.mainMenu = (this.ui.MainMenu as Menu) ?? new Menu();
                this.mainMenu.Items.Add(this.fileItem);
            }
        }

        /// <summary>
        /// Called when the title of this pane has changed and the UI should be updated
        /// </summary>
        private void OnTitleChanged()
        {
            if (this.TitleChanged != null)
                this.TitleChanged.Invoke(this.Title);
        }

        /// <summary>
        /// Gets the title of this pane
        /// <para/>
        /// Setting this value will result in a <see cref="NotImplementedException"/> being thrown
        /// </summary>
        public string Title
        {
            get
            {
                return this.title;
            }
            set
            {
                this.title = value;
                this.OnTitleChanged();
            }
        }

        /// <summary>
        /// Fired whenever the title of this pane changes (Which is currently... never)
        /// </summary>
        public event Action<string> TitleChanged;

        /// <summary>
        /// Not Implemented
        /// </summary>
        public void Close()
        {
            //throw new NotImplementedException();
        }
        
        /// <summary>
        /// Wrapper for DispatcherObject.CheckAccess()
        /// </summary>
        /// <returns>The same as DispatcherObject.CheckAccess()</returns>
        protected new bool CheckAccess()
        {
            return base.CheckAccess();
        }

        /// <summary>
        /// Used to write text to the logging box
        /// </summary>
        /// <param name="format">The message to write</param>
        /// <param name="colour">The colour of the message</param>
        /// <param name="args">The arguments to pass to <see cref="string.Format(string, object[])"/></param>
        private void WriteLine(string format, Brush colour, params object[] args)
        {
            if (!this.CheckAccess())
                this.Dispatcher.Invoke(new Action<string, Brush, object>((f, c, a) => this.WriteLine(f, c, (object[])a)), format, colour, (object)args);
            else
            {
                this.toplevelParagraph.Inlines.Add(new Run(string.Format(format, args) + Environment.NewLine) { Foreground = colour });
                this.messageBox1.ScrollToEnd();
            }
        }

        /// <summary>
        /// Writes the specified message to the logging box with the specified colour
        /// </summary>
        /// <param name="colour">The colour of the message</param>
        /// <param name="message">The message</param>
        private void WriteLine(Brush colour, string message)
        {
            this.toplevelParagraph.Inlines.Add(new Run(message + Environment.NewLine) { Foreground = colour });
            this.messageBox1.ScrollToEnd();
        }

        /// <summary>
        /// Returns false...
        /// </summary>
        public bool Closeable
        {
            get { return false; }
        }

        /// <summary>
        /// This method is called from <see cref="Core"/> when a packet from the server to an unknown channel has been received
        /// </summary>
        /// <param name="message">The message that was received</param>
        public void OnServerMessage(BasicMessage message)
        {
            if (!this.CheckAccess())
                this.Dispatcher.Invoke(new Action<BasicMessage>((b) => this.OnServerMessage(b)), message);
            else
                this.WriteLine(new SolidColorBrush(Colors.DarkBlue), message.UTF8Message);
        }

        /// <summary>
        /// Handler for the click event on the join button
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The event arguments</param>
        private void joinButton_Click(object sender, RoutedEventArgs e)
        {
            this.JoinSelected();
        }

        /// <summary>
        /// Joins the currently selected channel (if no selection is made or the channel requires a password, the JoinChannel dialog is shown)
        /// </summary>
        private void JoinSelected()
        {
            ChannelDescriptor descriptor;
            this.joinChannel.ChannelName = string.Empty;
            this.joinChannel.Password = string.Empty;
            if ((descriptor = this.channelView1.SelectedItem as ChannelDescriptor) != null)
                this.joinChannel.ChannelName = descriptor.ChannelName;
            this.ReplaceContent(this.joinChannel, "Enter Channel Details");
        }

        ///// <summary>
        ///// Storage for the old content of the <see cref="mainContentBox1"/>
        ///// </summary>
        //private object oldContent = null;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void createChannel_Cancel(object sender, RoutedEventArgs e)
        {
            modChannelID = 0;
            this.ResetContentBox();
        }

        /// <summary>
        /// Resets the content of the <see cref="mainContentBox1"/> to the stored values
        /// </summary>
        private void ResetContentBox()
        {
            if (this.backList.Count > 0)
            {
                object o = this.backList.LastOrDefault();
                this.mainContentBox1.Content = o;
                this.backList.RemoveAt(this.backList.Count - 1);
            }
            if (this.headerBackList.Count > 0)
            {
                object o = this.headerBackList.LastOrDefault();
                this.mainContentBox1.Header = o;
                this.headerBackList.RemoveAt(this.headerBackList.Count - 1);
            }
        }

        /// <summary>
        /// Used when the "Modify" button is clicked to indicate which channel should be modified
        /// </summary>
        int modChannelID = 0;

        /// <summary>
        /// Called when the Create channel dialog has been confirmed
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The arguments of the event</param>
        void createChannel_OK(object sender, RoutedEventArgs e)
        {
            this.ResetContentBox();
            ChannelControlMessage msg = this.createChannel.Message;
            Packet pkt = new Packet(this.core.GetID(Core.TYPE_CHANNELCTRL));
            if (this.core.CurrentUser != null)
                pkt.SourceUserID = this.core.CurrentUser.UserID;
            pkt.DestinationUserID = -1;
            
            if (modChannelID != 0)
                pkt.ChannelID = modChannelID;
            else
                pkt.ChannelID = -1;

            pkt.SerializeData(msg);
            this.core.Send(pkt);
            this.modChannelID = 0;
        }

        /// <summary>
        /// Called when the "Create" button has been clicked
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The arguments of the event</param>
        private void createButton_Click(object sender, RoutedEventArgs e)
        {
            this.createChannel.Message.Mode = ControlMode.Create;
            this.ReplaceContent(this.createChannel, "Create Channel");
        }

        /// <summary>
        /// Called when the "Refresh" button has been clicked
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The arguments of the event</param>
        private void refreshButton_Click(object sender, RoutedEventArgs e)
        {
            if (this.core != null)
                this.core.UpdateChannelList();
        }

        /// <summary>
        /// Called when the "Send" button has been clicked
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The arguments of the event</param>
        private void sendButton_Click(object sender, RoutedEventArgs e)
        {

        }

        /// <summary>
        /// Called when the "Modify" menu item has been clicked in the channel list's context menu
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The arguments of the event</param>
        private void clistModify1_Click(object sender, RoutedEventArgs e)
        {
            ChannelDescriptor descriptor;
            if ((descriptor = this.channelView1.SelectedItem as ChannelDescriptor) != null)
            {
                ChannelControlMessage msg = new ChannelControlMessage { ChannelName = descriptor.ChannelName };
                msg.Mode = ControlMode.Modify;
                this.createChannel.Message = msg;
                this.modChannelID = descriptor.ChannelID;
                this.ReplaceContent(this.createChannel, "Modify Channel");
            }
            else
                MessageBox.Show("Please select a channel before trying to modify it", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
        }

        /// <summary>
        /// Called when the "Delete" menu item has been clicked in the channel list's context menu
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The arguments of the event</param>
        private void clistDelete1_Click(object sender, RoutedEventArgs e)
        {
            ChannelDescriptor descriptor;
            ChannelControlMessage msg = new ChannelControlMessage { Mode = ControlMode.Delete };
            if ((descriptor = this.channelView1.SelectedItem as ChannelDescriptor) != null)
                msg.ChannelName = descriptor.ChannelName;
            this.createChannel.Message = msg;
            this.ReplaceContent(this.createChannel, "Delete Channel");
        }

        /// <summary>
        /// Called when the "Join" menu item has been clicked in the channel list's context menu
        /// </summary>
        /// <param name="sender">The sender of the event</param>
        /// <param name="e">The arguments of the event</param>
        private void clistJoin1_Click(object sender, RoutedEventArgs e)
        {
            this.JoinSelected();
        }
    }
}
